package com.sztx.pay.center.core.service.impl;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.validator.routines.UrlValidator;
import org.mortbay.log.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONObject;
import com.sztx.commons.httpclient.HttpClientConfigBuilder;
import com.sztx.commons.httpclient.HttpClientUtil;
import com.sztx.commons.httpclient.HttpHeaderBuilder;
import com.sztx.pay.center.common.util.ConfigUtil;
import com.sztx.pay.center.common.util.Constance.CallBackOrderState;
import com.sztx.pay.center.common.util.Constance.NotifyState;
import com.sztx.pay.center.common.util.Constance.TradeOrderType;
import com.sztx.pay.center.core.service.BillnoService;
import com.sztx.pay.center.core.service.CallBackService;
import com.sztx.pay.center.core.service.bean.CallBackInfo;
import com.sztx.pay.center.core.service.bean.NotifyModel;
import com.sztx.pay.center.dataaccess.dao.mysql.CallbackNotifyMysqlDAO;
import com.sztx.pay.center.dataaccess.domain.CallbackNotifyDO;
import com.sztx.pay.center.dataaccess.entity.QueryCallback;
import com.sztx.se.common.util.date.DateUtil;
import com.sztx.se.core.service.impl.BaseServiceImpl;
import com.sztx.se.dataaccess.mysql.config.PageQuery;
@Service
public class CallBackServiceImpl extends BaseServiceImpl implements CallBackService {
	private String charset = "utf-8";
	private String signType = "MD5";
	
	@Autowired
	private CallbackNotifyMysqlDAO callbackNotifyMysqlDAO;
	@Autowired
	private BillnoService billnoService;
	
	@Override
	@Async
	public void callBack(CallBackInfo callBackInfo) {
		String tradeOrderId = callBackInfo.getTradeOrderId();
		TradeOrderType orderType = callBackInfo.getOrderType();
		CallBackOrderState orderState = callBackInfo.getOrderState();
		
		
		CallbackNotifyDO callbackNotifyDO = callbackNotifyMysqlDAO.findByOrderId(tradeOrderId,orderType.getCode());
		
		
		if(callbackNotifyDO == null){
			UrlValidator validator = new UrlValidator();
			String notifyUrl = callBackInfo.getNotfiyUrl();
			if (notifyUrl == null || notifyUrl.isEmpty() || !validator.isValid(notifyUrl)) {
				Log.info("notify Url is blank,donot call back");
			}else{
				boolean needNotify = true;
				if(orderState == CallBackOrderState.EXCEPTION){
					Log.info("订单异常,记录订单,不进行订单回调   callBackInfo="+JSONObject.toJSONString(callBackInfo));
					needNotify = false;
				}
				save(callBackInfo,needNotify);
			}
		}
	}
	
	
	private String save(CallBackInfo callBackInfo,boolean needNotify){
		TradeOrderType orderType = callBackInfo.getOrderType();
		CallBackOrderState orderState = callBackInfo.getOrderState();
		String tradeOrderId = callBackInfo.getTradeOrderId();
		String outOrderId = callBackInfo.getOutOrderId();
		String notifyUrl = callBackInfo.getNotfiyUrl();
		String returnUrl = callBackInfo.getReturnUrl();
		String attach = callBackInfo.getAttach();
		Date tradeCrateTime = callBackInfo.getTradeCrateTime();
		Date payTime = callBackInfo.getPayTime();
		Integer payAmount = callBackInfo.getPayAmount();
		Integer totalFee = callBackInfo.getTotalFee();
		String orderDesc = callBackInfo.getOrderDesc();
		
		CallbackNotifyDO callbackNotifyDO = new CallbackNotifyDO();
		String id = billnoService.nexCallBackNo();
		callbackNotifyDO.setNotifyId(id);
		callbackNotifyDO.setOrderId(tradeOrderId);
		callbackNotifyDO.setOutTradeNo(outOrderId);
		callbackNotifyDO.setInputCharset(this.charset);
		callbackNotifyDO.setSignType(this.signType);
		callbackNotifyDO.setNotifyUrl(notifyUrl);
		callbackNotifyDO.setReturnUrl(returnUrl);
		callbackNotifyDO.setAttach(attach);
		callbackNotifyDO.setTradeCrateTime(tradeCrateTime);
		callbackNotifyDO.setPayTime(payTime);
		callbackNotifyDO.setCrateTime(new Date());
		callbackNotifyDO.setOrderType((byte)orderType.getCode());
		callbackNotifyDO.setTotalFee((long)totalFee);
		callbackNotifyDO.setPayAmount((long)payAmount);
		callbackNotifyDO.setOrderStatus((byte)orderState.getCode());
		callbackNotifyDO.setOrderDesc(orderDesc);
		callbackNotifyDO.setCount(new Byte("0"));
		callbackNotifyMysqlDAO.save(callbackNotifyDO );
		if(needNotify){
			CallbackNotifyDO callbackNotifyDO2 = new CallbackNotifyDO();
			callbackNotifyDO2.setStatus((byte)CallBackOrderState.ING.getCode());
			callbackNotifyDO2.setNotifyId(callbackNotifyDO.getNotifyId());
			List<CallbackNotifyDO> callbackNotifyDOList = new ArrayList<CallbackNotifyDO>();
			callbackNotifyDOList.add(callbackNotifyDO2);
			callbackNotifyMysqlDAO.updateState(callbackNotifyDOList);
			this.callBack(callbackNotifyDO);
		}
		return id;
	}


	@Override
	@Async
	public void callBack(CallbackNotifyDO callbackNotifyDO) {
		NotifyModel notifyModel = new NotifyModel();
		notifyModel.setTradeOrderId(callbackNotifyDO.getOrderId());
		notifyModel.setOutOrderId(callbackNotifyDO.getOutTradeNo());
		notifyModel.setTotalFee(callbackNotifyDO.getTotalFee().intValue());
		notifyModel.setPayAmount(callbackNotifyDO.getPayAmount().intValue());
		notifyModel.setAttach(callbackNotifyDO.getAttach());
		notifyModel.setPayTime(callbackNotifyDO.getPayTime());
		notifyModel.setOrderState(CallBackOrderState.valueOf(callbackNotifyDO.getOrderStatus()));
		notifyModel.setCharset(this.charset);
		notifyModel.setSignType(this.signType);
		notifyModel.setOrderDesc(callbackNotifyDO.getOrderDesc());
		notifyModel.setOrderType(callbackNotifyDO.getOrderType());
		String id = callbackNotifyDO.getNotifyId();
		
		//回调
		NotifyState notifyStatus = callBackToBusiness(notifyModel,callbackNotifyDO.getNotifyUrl(),id);
		
		//回调状态保存
		int count = callbackNotifyDO.getCount();
		CallbackNotifyDO callbackNotifyDO2 = new CallbackNotifyDO();
		callbackNotifyDO2.setNotifyId(id);
		callbackNotifyDO2.setStatus((byte)notifyStatus.getCode());
		count += 1;
		callbackNotifyDO2.setCount((byte)count);
		callbackNotifyMysqlDAO.update(callbackNotifyDO2);
		Log.info("notify end,notifyStatus:"+notifyStatus.getCode()+" orderId:"+callbackNotifyDO.getOrderId());
	
	}
	//回调业务系统   
	private NotifyState callBackToBusiness(NotifyModel notifyModel,String notfiyUrl,String id){
		Map<String, String> parasMap = new HashMap<String, String>();
		try {
			String payTime = DateUtil.formatDate(notifyModel.getPayTime(), "yyyyMMddHHmmss");
			parasMap = convertBean(notifyModel);
			parasMap.put("payTime", payTime);
			parasMap.put("orderState", String.valueOf(notifyModel.getOrderState().getCode()));
			String sign=createSign(parasMap,ConfigUtil.notifyKey);
			parasMap.put("sign",sign);
			return httpcallback(parasMap,notfiyUrl,id);
			//notifyModel.setSign(sign);
		} catch(RuntimeException e){
			LOG.info("回调通知业务系统失败",e);
			return NotifyState.FAIL;
		} catch (IntrospectionException e) {
			LOG.info("回调通知业务系统失败",e);
			return NotifyState.EXCEPTION;
		} catch (IllegalAccessException e) {
			LOG.info("回调通知业务系统失败",e);
			return NotifyState.EXCEPTION;
		} catch (InvocationTargetException e) {
			LOG.info("回调通知业务系统失败",e);
			return NotifyState.EXCEPTION;
		} catch (Exception e){
			LOG.info("回调通知业务系统失败",e);
			return NotifyState.EXCEPTION;
		}
		
	}

	//http回调业务系统
	private NotifyState httpcallback(Map<String, String> parasMap,String notfiyUrl,String id) throws RuntimeException{
		if(notfiyUrl == null){
			return NotifyState.FAIL;
		}
		
		
		String encoding = this.charset;
		HttpHeaderBuilder headerBuilder = HttpHeaderBuilder.custom();
		
		
		
		HttpClientConfigBuilder clientBuilder = HttpClientConfigBuilder.custom();
		String result = HttpClientUtil.post(clientBuilder, notfiyUrl, parasMap, headerBuilder, encoding);
		LOG.info("回调结果： result====="+result);
		
		String echoMessage = result;
		if(result.length() >= 255){
			echoMessage = result.substring(0, 254);
		}
		CallbackNotifyDO callbackNotifyDO2 = new CallbackNotifyDO();
		callbackNotifyDO2.setNotifyId(id);
		callbackNotifyDO2.setEchoMessage(echoMessage);
		callbackNotifyMysqlDAO.update(callbackNotifyDO2);
		
		if(result.equalsIgnoreCase("ok")){
			LOG.info("回调业务系统通知返回成功： parasMap"+JSONObject.toJSONString(parasMap));
			return NotifyState.SUCCESS;
		}else{
			LOG.info("回调业务系统通知返回失败： parasMap"+JSONObject.toJSONString(parasMap));
			return NotifyState.FAIL;
		}
	}
	
	
	/**
	 * 生成签名
	 * @param packageParams
	 * @param signKey
	 * @return
	 */
	public String createSign(Map<String, String> packageParams,
			String signKey) {
		SortedMap<String, String> sortedMap = new TreeMap<String, String>();
		sortedMap.putAll(packageParams);
	
		List<String> keys = new ArrayList<String>(packageParams.keySet());
		Collections.sort(keys);
	
		StringBuffer toSign = new StringBuffer();
		for (String key : keys) {
			String value = packageParams.get(key);
			if (null != value && !"".equals(value) && !"sign".equals(key) && !"key".equals(key)) {
				toSign.append(key + "=" + value + "&");
			}
		}
		Log.info("signStr=========="+toSign.toString());
		toSign.append("key=" + signKey);
		String sign = DigestUtils.md5Hex(toSign.toString()).toUpperCase();
		return sign;
	}
	/**
	 * 对象转map
	 * @param bean
	 * @return
	 * @throws IntrospectionException
	 * @throws IllegalAccessException
	 * @throws InvocationTargetException
	 */
	public Map<String,String> convertBean(Object bean)
	        throws IntrospectionException, IllegalAccessException, InvocationTargetException {
	    Class<? extends Object> type = bean.getClass();
	    Map<String,String> returnMap = new HashMap<String,String>();
	    BeanInfo beanInfo = Introspector.getBeanInfo(type);
	
	    PropertyDescriptor[] propertyDescriptors =  beanInfo.getPropertyDescriptors();
	    for (int i = 0; i< propertyDescriptors.length; i++) {
	        PropertyDescriptor descriptor = propertyDescriptors[i];
	        String propertyName = descriptor.getName();
	        if (!propertyName.equals("class")) {
	            Method readMethod = descriptor.getReadMethod();
	            Object result = readMethod.invoke(bean, new Object[0]);
	            if (result != null) {
	                returnMap.put(propertyName, result.toString());
	            } else {
	                returnMap.put(propertyName, "");
	            }
	        }
	    }
	    return returnMap;
	}


	@Override
	public List<CallbackNotifyDO> queryOrderCallBack(QueryCallback query,
			PageQuery pageQuery) {
		
		return callbackNotifyMysqlDAO.findCallback(query, pageQuery);
	}


	@Override
	public int countOrderCallBack(QueryCallback query) {
		
		return callbackNotifyMysqlDAO.countCallback(query);
	}
	
}
